#include "ncsproptree_p.h"
#include "ncspropmgr.h"
#include "ncspropedit.h"

#include <QHeaderView>
#include <QPainter>
#include <QApplication>
#include <QMouseEvent>
#include <QCheckBox>
#include <assert.h>

static const int LIGHTER_FACTOR = 150;  // qtpropertytreebrowser use 112 by default. 
static const QColor groupItemBg(138, 149, 165, 255); 

//--------------------------------------------------------------------------------------------------NcsPropTree {
NcsPropTree::NcsPropTree(QWidget* parent ) 
    : QTreeWidget(parent)
    , d ( new NcsPropTreePrivate(this))
{
    d->init(parent); 
    initWidget();
}


NcsPropTree::~NcsPropTree() 
{
    if (d){
        delete d; 
        d = nullptr; 
    }
}

QTreeWidgetItem* NcsPropTree::addProp(NcsProp* prop, NcsProp* prop_parent)
{
    auto it =  m_prop2item.find(prop);
    
    if (it != m_prop2item.end()) {
        return m_prop2item[prop];
    }

    // create new item. find parent item first. 

    // try to find item's parent  
    QTreeWidgetItem* parent_item = nullptr; 
    if (m_prop2item.contains(prop_parent))
        parent_item = m_prop2item[prop_parent];
    else
        parent_item = this->invisibleRootItem();

    QTreeWidgetItem* item = new QTreeWidgetItem ( parent_item,
         QStringList{
         prop->label(),
         prop->propType() == NcsPropMgr::groupTypeId() ? QString() : prop->valueText()
         }
        );

    item->setToolTip(0, prop->getAttribStr("tooltip"));
    item->setToolTip(1, prop->getAttribStr("valuetooltip"));
    QIcon value_icon =  prop->valueIcon();
    if (!value_icon.isNull())
    {
        item->setIcon(1, value_icon);
    }

    //item->setIcon(1, QIcon(":/demo/icon/demo.png"));

    // try to create item widget (if defined);
    if (QWidget* widget = d->createItemWidget(prop, this)) {
        item->setFlags(item->flags() &  (~Qt::ItemIsEditable));
        this->setItemWidget(item, 1, widget);
    }
    else {
        item->setFlags(item->flags() | Qt::ItemIsEditable);
    }
    m_prop2item[prop] = item;
    d->setItemProp(item, prop); 
    //item->setData(0, Qt::UserRole + 1, (uintptr_t)prop); 

    if (!prop->subProperties().isEmpty())
    {
        auto sub_props = prop->subProperties();
        for (auto sub_p : sub_props)
            this->addProp(sub_p, prop);
    }

	return item;
}

void NcsPropTree::clearPropTree(uint32_t clear_options )
{
    this->clear(); // remove all tree items. 
    m_prop2item.clear();
    d->resetState(clear_options );
    if (m_propmgr) {
        disconnect(m_propmgr, 0, this, 0);
        m_propmgr = nullptr;
    }

}

void NcsPropTree::updatePropView(NcsPropMgr* propmgr)
{
    clearPropTree(); // remove all items 
    if ( !propmgr ) return; 

    // clear modified status
    PropTraverse(propmgr->rootProp(), [](NcsProp* p) { p->setModified(false);});
    this->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
	NcsProp* root = propmgr->rootProp(); 
    for (auto p : root->subProperties())
        this->addProp(p, root);

    this->expandAll();
    this->header()->setSectionResizeMode(QHeaderView::Interactive);

    connect(propmgr, &NcsPropMgr::valueChanged, this, [=, this](NcsProp* prop, const QVariant& val)
	{
        QTreeWidgetItem* item = NULL;
        do {
            item = this->itemFromProperty(prop);
            if (!item) return;
            if (prop->propType() == NcsPropMgr::groupTypeId()) break;

            // update text and icon
            item->setText(1, prop->valueText());
            QIcon prop_icon = prop->valueIcon();
            if (!prop_icon.isNull())
                item->setIcon(1, prop_icon);

            // travel to parent
            item = item->parent();
            prop = d->itemProp(item);

        } while (item!=nullptr && prop!=nullptr);
	});

    connect(propmgr, &NcsPropMgr::propertyDestroying, this, [=, this](NcsProp* prop)
    {
        d->deleteItemProp(prop);
    });

    m_propmgr = propmgr;
}

QTreeWidgetItem* NcsPropTree::itemFromProperty(NcsProp* prop) const 
{
    auto it = m_prop2item.find(prop);
    if (it == m_prop2item.end())
        return nullptr; 

    return it.value();
}

void NcsPropTree::setBackgroundColor(QTreeWidgetItem* item, const QColor &color)
{
	d->setBackgroundColor(item, color);
}


void NcsPropTree::setMarkNullProp(bool mark)
{
    d->m_markNullProp = mark;  
}

void NcsPropTree::setPropEditorFactory(NcsProp* prop, NcsPropMgr::EditorFn editor_factory)
{
    d->m_itemwidget_factory_for_prop[prop] = editor_factory; 
}
void NcsPropTree::setTypeEditorFactory(int prop_type, NcsPropMgr::EditorFn editor_factory)
{
    d->m_itemwidget_factory_for_type[prop_type] = editor_factory; 
}

//--------------------------------------------------------------------------------------------------non-public

void NcsPropTree::initWidget()
{
    // 2 column  : Property, Value
    this->setColumnCount(2);
    this->setHeaderLabels({"Property", "Value"});
    this->header()->setStretchLastSection(true);
    this->header()->setSectionsMovable(false);
    //this->header()->setSectionResizeMode(0, QHeaderView::Stretch);
    this->setIndentation(16);
    //this->setRootIsDecorated(false);
    this->setAlternatingRowColors(true);
    /** set alternate color.
    QPalette p = palette();
    p.setColor( QPalette::AlternateBase, QColor(226, 237, 253) );
    setPalette(p);
    */
    
    this->setIconSize(QSize(18, 18));
    this->setEditTriggers(QAbstractItemView::EditKeyPressed); 

    // set default persistent editor

    this->setTypeEditorFactory(NcsPropMgr::boolTypeId(), [](NcsProp* prop) {
		    return new NpBoolEdit(prop);
        });


    /*
    QObject::connect(this, SIGNAL(collapsed(QModelIndex)), q_ptr, SLOT(slotCollapsed(QModelIndex)));
    QObject::connect(this, SIGNAL(expanded(QModelIndex)), q_ptr, SLOT(slotExpanded(QModelIndex)));
    QObject::connect(this, SIGNAL(currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)), q_ptr, SLOT(slotCurrentTreeItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)));
    QObject::connect(this, & QTreeWidget::itemEntered,
            [this] (QTreeWidgetItem* item, int col)
            {
                auto it  = this->m_itemToIndex.find(item) ;
                if (it != this->m_itemToIndex.end()) {
                    QtBrowserItem* item  = it.value();
                    q_ptr->emitItemHovered(item);
                }
            }
            );
    */
}

void NcsPropTree::drawRow(QPainter *painter, const QStyleOptionViewItem &option, 
                 const QModelIndex &index) const 
{
    QStyleOptionViewItem opt = option;
    bool hasValue = true;
    NcsProp* prop = d->indexToProperty(index);
    if (prop)
        hasValue = prop->hasValue();

    //if ( !hasValue && d->markNullProp()) {
    if (prop->propType() == NcsPropMgr::groupTypeId()) {
        const QColor c = groupItemBg;
        painter->fillRect(option.rect, c);
        opt.palette.setColor(QPalette::AlternateBase, c);
    }
    else {
        const QColor c = d->calculatedBackgroundColor(this->itemFromIndex(index));
        if (c.isValid()) {
            painter->fillRect(option.rect, c);
            opt.palette.setColor(QPalette::AlternateBase, c.lighter(LIGHTER_FACTOR));
        }
    }
    QTreeWidget::drawRow(painter, opt, index);
    QColor color = static_cast<QRgb>(QApplication::style()->styleHint(QStyle::SH_Table_GridLineColor, &opt));
    painter->save();
    painter->setPen(QPen(color));
    painter->drawLine(opt.rect.x(), opt.rect.bottom(), opt.rect.right(), opt.rect.bottom());
    painter->restore();
}
/*
  Start edit item if left mouse button clicked. 
*/
void NcsPropTree::mousePressEvent(QMouseEvent *event)
{
    QTreeWidget::mousePressEvent(event);
    QTreeWidgetItem *item = this->itemAt(event->pos());
    
    if (item) {
        if ((item != d->editedItem()) && (event->button() == Qt::LeftButton)
                && (header()->logicalIndexAt(event->pos().x()) == 1)
                && ((item->flags() & (Qt::ItemIsEditable | Qt::ItemIsEnabled)) == (Qt::ItemIsEditable | Qt::ItemIsEnabled))) {
            editItem(item, 1);
        } else if (!d->hasValue(item) && d->markNullProp() && !rootIsDecorated()) {
            if (event->pos().x() + header()->offset() < 20)
                item->setExpanded(!item->isExpanded());
        }
    }
}

/*
  Start edit item if enter key / space key pressed.
*/
void NcsPropTree::keyPressEvent(QKeyEvent *event)
{
    switch (event->key()) {
    case Qt::Key_Return:
    case Qt::Key_Enter:
    case Qt::Key_Space: // Trigger Edit
        if (!d->editedItem())
            if (const QTreeWidgetItem *item = currentItem())
                if (item->columnCount() >= 2 && ((item->flags() & (Qt::ItemIsEditable | Qt::ItemIsEnabled)) == (Qt::ItemIsEditable | Qt::ItemIsEnabled))) {
                    event->accept();
                    // If the current position is at column 0, move to 1.
                    QModelIndex index = currentIndex();
                    if (index.column() == 0) {
                        index = index.sibling(index.row(), 1);
                        setCurrentIndex(index);
                    }
                    edit(index);
                    return;
                }
        break;
    default:
        break;
    }
    QTreeWidget::keyPressEvent(event);
}

//NcsPropTree end }

//--------------------------------------------------------------------------------------------------NcsPropTreePrivate
void NcsPropTreePrivate::init(QWidget *parent)
{
    m_delegate = new NcsPropEditorDelegate(this, parent);
    q->setItemDelegate(m_delegate);

    m_expandIcon = drawIndicatorIcon(q->palette(), q->style());
    
}

QTreeWidgetItem *NcsPropTreePrivate::editedItem() const
{
    return m_delegate->editedItem();
}

bool NcsPropTreePrivate::hasValue(QTreeWidgetItem *item) const
{
    NcsProp* prop = this->itemProp(item);
    if (prop)
        return prop->hasValue();
    return false;
}

//--------------------------------------------------------------------------------------------------NcsPropEditorDelegate

int NcsPropEditorDelegate::indentation(const QModelIndex &index) const
{
    if (!m_treepriv)
        return 0;

    QTreeWidgetItem *item = m_treepriv->indexToItem(index);
    int indent = 0;
    while (item->parent()) {
        item = item->parent();
        ++indent;
    }
    if (m_treepriv->treeWidget()->rootIsDecorated())
        ++indent;
    return indent * m_treepriv->treeWidget()->indentation();
}

void NcsPropEditorDelegate::slotEditorDestroyed(QObject *object)
{
    if (QWidget *w = qobject_cast<QWidget *>(object)) {
        const auto it = m_edit2prop.find(w);
        if (it != m_edit2prop.end()) {
            m_prop2edit.remove(it.value());
            m_edit2prop.erase(it);
        }
        if (m_editedWidget == w) {
            m_editedWidget = 0;
            m_editedItem = 0;
        }
    }
}

void NcsPropEditorDelegate::closeEditor(NcsProp *prop)
{
    if (QWidget *w = m_prop2edit.value(prop, 0))
        w->deleteLater();
}

QWidget *NcsPropEditorDelegate::createEditor(QWidget *parent,
        const QStyleOptionViewItem &, const QModelIndex &index) const
{
    if (index.column() == 1 ) 
    {
        NcsProp *prop = m_treepriv->indexToProperty(index);
        QTreeWidgetItem *item = m_treepriv->indexToItem(index);
        if (prop && item && (item->flags() & Qt::ItemIsEnabled)) 
        {
            QWidget* editor =  m_treepriv->createEditor(prop, parent);
            if (editor) {
                editor->setAutoFillBackground(true);
                editor->installEventFilter(const_cast<NcsPropEditorDelegate *>(this));
                connect(editor, SIGNAL(destroyed(QObject*)), this, SLOT(slotEditorDestroyed(QObject*)));
                m_prop2edit[prop] = editor;
                m_edit2prop[editor] = prop;
                m_editedItem = item;
                m_editedWidget = editor;
            }
            return editor;
        }
    }
    return 0;
}

void NcsPropEditorDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, 
        const QModelIndex &index) const
{
    Q_UNUSED(index)
    editor->setGeometry(option.rect.adjusted(0, 0, 0, -1));
}

void NcsPropEditorDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option,
            const QModelIndex &index) const
{
    NcsProp *prop = m_treepriv->indexToProperty(index);
    bool hasValue = prop->hasValue();

    // --------------------item text color and font weight (column -0)--------------------
    QStyleOptionViewItem opt = option;
    //if ((m_treepriv && index.column() == 0) || !hasValue) {
    if ((m_treepriv && index.column() == 0) && prop->propType()!=NcsPropMgr::groupTypeId())
    {
        if (prop && prop->isModified()) {
            opt.font.setBold(true);
            opt.fontMetrics = QFontMetrics(opt.font);
        }

        if (!hasValue && m_treepriv->markNullProp())
            opt.palette.setColor(QPalette::Text, Qt::red);
        else // null value. 
            opt.palette.setColor(QPalette::Text, opt.palette.color(QPalette::Text));
    }

    // --------------------item background--------------------
    QColor c;
    //if (!hasValue && m_treepriv->markNullProp()) {
    if (prop->propType() == NcsPropMgr::groupTypeId()) {
        c = groupItemBg;
        opt.palette.setColor(QPalette::Text, opt.palette.color(QPalette::BrightText));
        opt.font.setBold(true);
    } else {
        c = m_treepriv->calculatedBackgroundColor(m_treepriv->indexToItem(index));
        if (c.isValid() && (opt.features & QStyleOptionViewItem::Alternate))
            c = c.lighter(LIGHTER_FACTOR);
    }
    if (c.isValid())
        painter->fillRect(option.rect, c);

    // --------------------item boundary line :---------------------------
    opt.state &= ~QStyle::State_HasFocus;
    QItemDelegate::paint(painter, opt, index);
    opt.palette.setCurrentColorGroup(QPalette::Active);
    QColor color = static_cast<QRgb>(QApplication::style()->styleHint(QStyle::SH_Table_GridLineColor, &opt));
    painter->save();
    painter->setPen(QPen(color));
    //if (!m_treepriv || (!m_treepriv->lastColumn(index.column()) && hasValue)) {
    if (!m_treepriv || (!m_treepriv->lastColumn(index.column()) && prop->propType()!=NcsPropMgr::groupTypeId())) 
    { // no left boundary for group-type item. 
        int right = (option.direction == Qt::LeftToRight) ? option.rect.right() : option.rect.left();
        painter->drawLine(right, option.rect.y(), right, option.rect.bottom());
    }

    painter->restore();
}

QSize NcsPropEditorDelegate::sizeHint(const QStyleOptionViewItem &option,
            const QModelIndex &index) const
{
    return QItemDelegate::sizeHint(option, index) + QSize(3, 4);
}

bool NcsPropEditorDelegate::eventFilter(QObject *object, QEvent *event)
{
    if (event->type() == QEvent::FocusOut) {
        QFocusEvent *fe = static_cast<QFocusEvent *>(event);
        if (fe->reason() == Qt::ActiveWindowFocusReason)
            return false;
    }
    return QItemDelegate::eventFilter(object, event);
}


